﻿using Autofac;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using WebTest.Core.Entities;
using WebTest.Core.Entities.Interfaces;
using WebTest.Infrastructure.Data;
using WebTest.Infrastructure.Interfaces;

namespace WebTest.Infrastructure.Services
{
    public class Repository<T> : IRepository<T>
        where T : BaseEntity
    {
        private readonly OrderContext context;
        public Repository(OrderContext context)
        {
            this.context = context ?? throw new ArgumentNullException(nameof(context));
        }
        public async Task<T> AddAsync(T entity, CancellationToken cancellationToken = default)
        {
            if (entity == null) throw new ArgumentNullException(nameof(entity));

            await context.AddAsync(entity, cancellationToken);
            await context.SaveChangesAsync(cancellationToken);

            return entity;
        }

        public async Task<IReadOnlyCollection<T>> AddRangeAsync(IEnumerable<T> entites, CancellationToken cancellationToken = default)
        {
            if (entites == null) throw new ArgumentNullException(nameof(entites));

            await context.AddRangeAsync(entites, cancellationToken);
            await context.SaveChangesAsync(cancellationToken);

            return entites.ToList().AsReadOnly();
        }

        public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default)
        {
            if (entity == null) throw new ArgumentNullException(nameof(entity));

            context.Remove(entity);
            await context.SaveChangesAsync(cancellationToken);
        }

        public Task DeleteRangeAsync(List<int> list)
        {
            if (list == null || list.Count == 0) throw new ArgumentNullException(nameof(list));

            var entities = context.Set<T>()
                .Where(x => list.Contains(x.Id));
            context.RemoveRange(entities);

            return Task.CompletedTask;
        }

        public bool Exists(Expression<Func<T, bool>> predicate)
        {
            return context.Set<T>().AsNoTracking().Any(predicate);
        }

        public async Task<bool> ExistsAsync(Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default)
        {
            return await context.Set<T>().AsNoTracking().AnyAsync(predicate, cancellationToken);
        }

        public async Task<T> FirstOrDefault(Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default)
        {
            var query = context.Set<T>().AsQueryable();
            foreach (var nav in context.Model.FindEntityType(typeof(T)).GetNavigations())
            {
                query.Include(nav.Name);
            }

            return await query.FirstOrDefaultAsync(predicate, cancellationToken);
        }

        public async Task<T> SingleOrDefault(Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default)
        {
            var query = context.Set<T>().AsQueryable();
            foreach(var nav in context.Model.FindEntityType(typeof(T)).GetNavigations())
            {
                query.Include(nav.Name);
            }

            return await query.SingleOrDefaultAsync(predicate, cancellationToken);
        }

        public async Task<IReadOnlyCollection<T>> ToListAsync(Expression<Func<T, bool>> predicate = null, CancellationToken cancellationToken = default)
        {
            IQueryable<T> query = context.Set<T>().AsQueryable();
            foreach (var nav in context.Model.FindEntityType(typeof(T)).GetNavigations())
            {
                query.Include(nav.Name);
            }
            if (predicate != null)
                query = query.Where(predicate);

            return await query.ToListAsync(cancellationToken);
        }
        public async Task<IReadOnlyCollection<T>> ToListAsync(int pageSize = 20, int current = 1, CancellationToken cancellationToken = default)
        {
            var query = context.Set<T>().AsQueryable();
            foreach (var nav in context.Model.FindEntityType(typeof(T)).GetNavigations())
            {
                query.Include(nav.Name);
            }
            var result = query
                .AsNoTracking()
                .OrderBy(x => x.Id)
                .Skip((current - 1) * pageSize)
                .Take(pageSize);
            var val = await result.ToListAsync();
            return val.AsReadOnly();
        }

        public async Task<T> UpdateAsync(T entity, CancellationToken cancellationToken = default)
        {
            context.Entry(entity).State = EntityState.Modified;
            await context.SaveChangesAsync(cancellationToken);

            return entity;
        }

        public async Task<T> LastOrderAsync(CancellationToken cancellationToken = default)
        {
            var query = context.Set<T>().AsQueryable();
            foreach (var nav in context.Model.FindEntityType(typeof(T)).GetNavigations())
            {
                query.Include(nav.Name);
            }
            return await query.LastOrDefaultAsync(cancellationToken);
        }
    }
}
